/*
	DS Kanji Flash
	Copyright (C) 2009,2010 Bernhard Schelling

	This file is part of DS Kanji Flash.

	DS Kanji Flash is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	DS Kanji Flash is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with DS Kanji Flash.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "global.h"
#include "dict.h"

#include "../build/kanjipool.h"
#include "../build/kanapool.h"
#include "../build/metadict.h"
#include "../build/stroke.h"

#if DRAW_MAX_POINTS < CHARS_MAX_POINTS
#error DRAW_MAX_POINTS is smaller than CHARS_MAX_POINTS
#endif
#if DRAW_MAX_STROKES < CHARS_MAX_STROKES
#error DRAW_MAX_STROKES is smaller than CHARS_MAX_STROKES
#endif

static bool bDrawing = false, bDragEnabled = false;
static s8 cDrawnNum = 0;
static u8 cDrawnPos[DRAW_MAX_STROKES];
static u8 cDrawnStrokes[DRAW_MAX_STROKES][DRAW_MAX_POINTS][2];
static UL_IMAGE* imgDrawn = NULL;

bool bCharacterEditMode = false;

#define DRAWX ((256-128)/2)
#define DRAWY (192-128-10)

const char* GetDict(int iKanji, int iDict)
{
	return (iDict >= METANUMKANJIDICTS ? NULL : metadict[iKanji][iDict]);
}

const u8* GetCharStrokes(wchar_t wcChar, bool bGetAllVariants)
{
	int i = GetKanjiIndex(wcChar);
	return (i>=0?kanji_stroke[i]+(bGetAllVariants?0:1):(wcChar>=0x3000&&wcChar<=0x30FF?kana_stroke[wcChar-0x3000]+(bGetAllVariants?0:1):NULL));
}

void GetCompSubPoints(u8 points[JUDGE_PREC][2], const u8 pStroke[][2], u8 cNumPoints, float fScale = 1.0)
{
	if (cNumPoints==0) { memset(points, 0, JUDGE_PREC*2); return; }
	if (cNumPoints==1) { for(int i=0;i<JUDGE_PREC;i++) { points[i][0] = pStroke[0][0]*fScale; points[i][1] = pStroke[0][1]*fScale; } return; }
	int segments = 0, nextseg = 0, npoints = 1;
	float fLengths[DRAW_MAX_POINTS], fLenTotal = 0, fSubTotal = 0;
	for (const u8 *p = pStroke[0]; p < pStroke[cNumPoints-1]; p+=2)
		fLenTotal += (fLengths[segments++] = LEN(p[0],p[1],p[2],p[3]));
	if (fLenTotal==0) { for(int i=0;i<JUDGE_PREC;i++) { points[i][0] = pStroke[0][0]*fScale; points[i][1] = pStroke[0][1]*fScale; } return; }
	float fStep = fLenTotal/JUDGE_PREC;
	for (float sub = fStep; sub < fLenTotal; sub += fStep,npoints++)
	{
		if (fSubTotal < sub) while ((fSubTotal += fLengths[nextseg++]) < sub);
		float segpos = 1.0-((fSubTotal - sub)/fLengths[nextseg-1]);
		const u8 *p = pStroke[nextseg-1];
		points[npoints][0] = (p[0] + (p[2]-p[0])*segpos)*fScale;
		points[npoints][1] = (p[1] + (p[3]-p[1])*segpos)*fScale;
	}
	points[0][0] = pStroke[0][0]*fScale;            points[0][1] = pStroke[0][1]*fScale;
	points[9][0] = pStroke[cNumPoints-1][0]*fScale; points[9][1] = pStroke[cNumPoints-1][1]*fScale;
}

void GetOffsetScale(u8 *ref, u8 *myd, u8 *refEnd, float refscale[], int refoff[])
{
	u8 refrect[4] = { 127, 127, 0, 0 }, mydrect[4] = { 127, 127, 0, 0 };
	for (u8 *refp = ref, *mydp = myd; refp != refEnd; refp += 2, mydp += 2)
		refrect[0] = ulMin(refp[0], refrect[0]), refrect[1] = ulMin(refp[1], refrect[1]), refrect[2] = ulMax(refp[0], refrect[2]), refrect[3] = ulMax(refp[1], refrect[3]),
		mydrect[0] = ulMin(mydp[0], mydrect[0]), mydrect[1] = ulMin(mydp[1], mydrect[1]), mydrect[2] = ulMax(mydp[0], mydrect[2]), mydrect[3] = ulMax(mydp[1], mydrect[3]);
	for (u8 *refp = ref; refp != refEnd; refp+=2)
		refp[0] -= refrect[0], refp[1] -= refrect[1];
	refscale[0] = ((float)(ulMax(mydrect[2]-mydrect[0]+5,(refrect[2]-refrect[0]+5)/2)))/((float)(refrect[2]-refrect[0]+5));
	refscale[1] = ((float)(ulMax(mydrect[3]-mydrect[1]+5,(refrect[3]-refrect[1]+5)/2)))/((float)(refrect[3]-refrect[1]+5));
	refoff[0] = myd[0]-(ref[0]*refscale[0]);
	refoff[1] = myd[1]-(ref[1]*refscale[1]);
}

float GetDrawScore(const u8 kanji_strokes[], u8 correction[DRAW_MAX_STROKES][JUDGE_PREC][2])
{
	float fScore = 0;
	float allrefscale[2];
	int allrefoff[2];
	u8 refpoints[DRAW_MAX_STROKES][JUDGE_PREC][2];
	u8 mydpoints[DRAW_MAX_STROKES][JUDGE_PREC][2];

	s8 iStrokes = ulMin(cDrawnNum, kanji_strokes[0]);
	
	const u8 *pStroke = kanji_strokes+1;
	for (u8 s = 0; s < iStrokes; s++, pStroke += pStroke[0]*2+1)
		GetCompSubPoints(refpoints[s], (u8(*)[2])(pStroke+1), *pStroke, 0.5);			
	
	for (u8 s = 0; s < iStrokes; s++)
		GetCompSubPoints(mydpoints[s], cDrawnStrokes[s], cDrawnPos[s]);
	
	GetOffsetScale(refpoints[0][0], mydpoints[0][0], refpoints[iStrokes][0], allrefscale, allrefoff);
	
	if (correction) memset(correction, 0xFF, DRAW_MAX_STROKES*JUDGE_PREC*2);

	for (u8 s = 0; s < iStrokes; s++)
	{
		int strokescorex = allrefoff[0]+(refpoints[s][0][0]*allrefscale[0]);
		int strokescorey = allrefoff[1]+(refpoints[s][0][1]*allrefscale[1]);
		fScore += 3*LEN(mydpoints[s][0][0], mydpoints[s][0][1], strokescorex, strokescorey);
		//ulDrawLine(dx+mydpoints[s][0][0], dy+mydpoints[s][0][1], dx+strokescorex, dy+strokescorey, COL_RED);
		//ulDrawLine(dx+mydpoints[s][0][0], dy+mydpoints[s][0][1]+1, dx+strokescorex, dy+strokescorey+1, COL_RED);
		//ulDrawLine(dx+mydpoints[s][0][0], dy+mydpoints[s][0][1]+2, dx+strokescorex, dy+strokescorey+2, COL_RED);
		
		if ((correction)/* && (cDrawnNum > kanji_strokes[0] || (cDrawnNum == kanji_strokes[0] && !bDrawing))*/)
			for (int i = 0; i < JUDGE_PREC; i++)
				correction[s][i][0] = DRAWX+allrefoff[0]+(refpoints[s][i][0]*allrefscale[0]),
				correction[s][i][1] = DRAWY+allrefoff[1]+(refpoints[s][i][1]*allrefscale[1]);
			
		float refscale[2];
		int refoff[2];
		GetOffsetScale(refpoints[s][0], mydpoints[s][0], refpoints[s+1][0], refscale, refoff);
		
		for (int i = 0; i < JUDGE_PREC; i++)
		{
			refpoints[s][i][0] = refoff[0]+(refpoints[s][i][0]*refscale[0]);
			refpoints[s][i][1] = refoff[1]+(refpoints[s][i][1]*refscale[1]);
			fScore += LEN(mydpoints[s][i][0], mydpoints[s][i][1], refpoints[s][i][0], refpoints[s][i][1]);
		}
	}
	fScore /= JUDGE_PREC*iStrokes;
	fScore += 10*ulAbs(cDrawnNum-kanji_strokes[0]);
	return fScore;	
}

float GetDrawScoreVariants(const u8 *kanji_variants)
{
	float fSmallestScore = 500;
	int iNumVariants = *(kanji_variants++);
	for (int v=0;v<iNumVariants;v++)
	{
		float fScore = GetDrawScore(kanji_variants);
		if (fScore < fSmallestScore) fSmallestScore = fScore;
		for (int iStrokes = *(kanji_variants++), s = 0; s < iStrokes; s++, kanji_variants += kanji_variants[0]*2+1);
	}
	return fSmallestScore;
}

wchar_t GuessChar()
{
	float fSmallestScore = 500;
	wchar_t wcBest = 0;
	for (const wchar_t *pChar = chars_by_stroke[cDrawnNum]; *pChar; pChar++)
	{
		const u8* pStrokes = GetCharStrokes(*pChar, true);
		if (!pStrokes) continue;
		int iNumVariants = *(pStrokes++);
		for (int v=0;v<iNumVariants;v++)
		{
			float fScore = GetDrawScore(pStrokes);
			if (fScore < fSmallestScore) { fSmallestScore = fScore; wcBest = *pChar; }			
			for (int iStrokes = *(pStrokes++), s = 0; s < iStrokes; s++, pStrokes += pStrokes[0]*2+1);
		}
	}
	return wcBest;
}

void DrawClearImage(bool bUseEditMode)
{
	cDrawnNum = 0;
	if (!imgDrawn) imgDrawn = ulCreateImage(128, 128, UL_IN_RAM, UL_PF_PAL2, 2);
	else
	{
		ulUnrealizeImage(imgDrawn);
		memset(imgDrawn->texture, 0<<0|0<<2|0<<4|0<<6, ((imgDrawn->sysSizeY*imgDrawn->sysSizeX)>>2));
	}
	((UL_COLOR*)imgDrawn->palette)[1] = COL_BLACK;
	ulRealizeImage(imgDrawn);
	bDrawing = false;
	bCharacterEditMode = bUseEditMode;
}

void ImgDrawLine(int x1, int y1, int x2, int y2)
{
	int w = x2-x1, h = y2-y1;
	float step = 0.666/LENSIZE(w, h);
	for (float f=0; f<=1.0; f+=step)
	{
		s16 x = x1+w*f, y = y1+h*f;
		u8* line = (u8*)imgDrawn->texture+(y-1)*(imgDrawn->sysSizeX>>2);
		for (u8 c = 0; c < 9; c++)
			if ((u16)(x-1+(c%3))<128&&(u16)(y-1+(c/3))<128) 
				//line[((x-1+(c%3))>>2)+((imgDrawn->sysSizeX>>2)*(c/3))] &= ~(3 << (((x-1+(c%3))&3) << 1)),
				line[((x-1+(c%3))>>2)+((imgDrawn->sysSizeX>>2)*(c/3))] |= 1 << (((x-1+(c%3))&3) << 1);
	}
}

void DrawRedrawImage()
{
	if (!imgDrawn) DrawClearImage();
	ulUnrealizeImage(imgDrawn);
	memset(imgDrawn->texture, 0<<0|0<<2|0<<4|0<<6, ((imgDrawn->sysSizeY*imgDrawn->sysSizeX)>>2));
	for (short s = 0; s < cDrawnNum; s++)
		for (u8 *p = cDrawnStrokes[s][0]; p < cDrawnStrokes[s][cDrawnPos[s]-1]; p+=2)
			ImgDrawLine(p[0], p[1], p[2], p[3]);
	ulRealizeImage(imgDrawn);
}

void DrawProcessInput()
{
	if (ul_keys.pressed.touch && sRect(256 - 53, 160, 42, 18).touchButton()) { cDrawnNum = 0; bDrawing = false; DrawRedrawImage(); }
	if (ul_keys.pressed.touch && sRect(256 - 53, 130, 42, 10).touchButton()) { bDragEnabled ^= 1; }
	if (ul_keys.pressed.left && cDrawnNum) { cDrawnNum--; bDrawing = false; DrawRedrawImage(); }
	if (ul_keys.pressed.right && cDrawnPos[cDrawnNum]) { cDrawnNum++; bDrawing = false; DrawRedrawImage(); }
	if (bDragEnabled && !bDrawing)
	{
		int tx = ul_keys.touch.x - DRAWX, ty = ul_keys.touch.y - DRAWY;
		tx = ulMin(ulMax(0, tx), 127); ty = ulMin(ulMax(0, ty), 127);
		static u8 *pDragPoint = NULL;
		if (ul_keys.released.touch && pDragPoint)
		{
			if (ul_keys.touch.x < DRAWX-3 && ul_keys.touch.y < DRAWY-3)
				for (u8 s = 0, bReWrite = 0; s < cDrawnNum && pDragPoint; s++)
					for (u8 *p = cDrawnStrokes[s][0]; p < cDrawnStrokes[s][cDrawnPos[s]]; p+=2)
						if (p==pDragPoint&&cDrawnPos[s]>2) { pDragPoint = NULL; bReWrite = 1; cDrawnPos[s]--; p[0] = p[2]; p[1] = p[3]; }
						else if (bReWrite) { p[0] = p[2]; p[1] = p[3]; }
			pDragPoint = NULL;
			DrawRedrawImage();
		}
		else if (pDragPoint) { pDragPoint[0] = tx; pDragPoint[1] = ty; }
		else if (ul_keys.pressed.touch)
			for (short s = 0; s < cDrawnNum; s++)
				for (u8 *p = cDrawnStrokes[s][0]; p < cDrawnStrokes[s][cDrawnPos[s]]; p+=2)
					if (tx>=p[0]-4&&tx<=p[0]+4&&ty>=p[1]-4&&ty<=p[1]+4)
					{ pDragPoint = p; s = cDrawnNum; break; }
		return;
	}
	if (ul_keys.pressed.touch && DRAW_MAX_STROKES > cDrawnNum && (u16)(ul_keys.touch.x - DRAWX) < 128 && (u16)(ul_keys.touch.y - DRAWY) < 128)
	{
		bDrawing = true;
		cDrawnPos[cDrawnNum++] = 0;
	}
	if (bDrawing && ul_keys.touch.held)
	{
		int tx = ul_keys.touch.x - DRAWX, ty = ul_keys.touch.y - DRAWY;
		tx = ulMin(ulMax(0, tx), 127); ty = ulMin(ulMax(0, ty), 127);
		if (cDrawnPos[cDrawnNum-1] == 0) { cDrawnStrokes[cDrawnNum-1][0][0] = tx; cDrawnStrokes[cDrawnNum-1][0][1] = ty; cDrawnPos[cDrawnNum-1] = 2; }
		if (cDrawnStrokes[0][cDrawnPos[cDrawnNum-1]] < cDrawnStrokes[1][0]-1)
		{
			u8 *pD = cDrawnStrokes[cDrawnNum-1][cDrawnPos[cDrawnNum-1]-2];
			pD[2] = tx; pD[3] = ty;
			if (LEN(pD[0],pD[1],pD[2],pD[3]) > 15) { cDrawnPos[cDrawnNum-1]++; pD[4] = tx; pD[5] = ty; }
			
			if (!imgDrawn) DrawClearImage();
			ulUnrealizeImage(imgDrawn);
			ImgDrawLine(tx, ty, tx-ul_keys.touch.deltaX, ty-ul_keys.touch.deltaY);
			ulRealizeImage(imgDrawn);
		}
	}
	if (ul_keys.released.touch) bDrawing = false;
}

void DrawDisplay(const u8 *pStrokeGuide, UL_IMAGE *imgOverlay)
{
	ulDrawGradientRect(DRAWX-2, DRAWY-2, DRAWX+128+2, DRAWY+128+2, RGB15(2,2,2), RGB15(2,2,2), RGB15(8,8,8), RGB15(8,8,8));
	ulDrawFillRect(DRAWX-0, DRAWY-0, DRAWX+128+0, DRAWY+128+0, COL_WHITE);
	ulDrawLine(DRAWX + 64, DRAWY, DRAWX + 64, DRAWY+128, RGB15(15,15,15));
	ulDrawLine(DRAWX, DRAWY + 64, DRAWX+128, DRAWY + 64, RGB15(15,15,15));

	if (pStrokeGuide)
	{
		const u8 *pStroke = pStrokeGuide+1;
		for (u8 s = 0; s < pStrokeGuide[0]; s++)
		{
			u8 cNumPoints = *(pStroke++), px = *(pStroke++)*128/256+DRAWX, py = *(pStroke++)*128/256+DRAWY;
			for (int p = 1; p < cNumPoints; p++)
			{
				u8 x = *(pStroke++)*128/256+DRAWX, y = *(pStroke++)*128/256+DRAWY;
				glDrawLine(px, py, x, y, 3, 3, (p==1&&s==cDrawnNum-(bDrawing?1:0)?RGB15(31,20,3):RGB15(15,15,15)), (s==cDrawnNum-(bDrawing?1:0)?RGB15(23,17,7):RGB15(15,15,15)), 0);
				px = x; py = y;
			}
		}
	}

	if (imgOverlay) ulDrawImageXY(imgOverlay, DRAWX, DRAWY);

	if (imgDrawn) ulDrawImageXY(imgDrawn, DRAWX, DRAWY);

	if (bCharacterEditMode)
	{
		for (short s = 0; s < cDrawnNum; s++)
			for (u8 *p = cDrawnStrokes[s][0]; p < cDrawnStrokes[s][cDrawnPos[s]]; p+=2)
				ulDrawLine(DRAWX+p[0]-2,DRAWY+p[1]-2,DRAWX+p[0]+2,DRAWY+p[1]+2, COL_RED),
				ulDrawLine(DRAWX+p[0]+2,DRAWY+p[1]-2,DRAWX+p[0]-2,DRAWY+p[1]+2, COL_RED);

		char info[16];
		displayString(ul_lcdFont, "Strokes:", 256 - 53, 53);
		sprintf(info, "%d", cDrawnNum);
		displayString(ul_lcdFont, info, 256 - 43, 67);

		int iPoints = 0;
		for (short s = 0; s < cDrawnNum; s++) iPoints += cDrawnPos[s];
		displayString(ul_lcdFont, "Points:", 256 - 53, 87);
		sprintf(info, "%d", iPoints);
		displayString(ul_lcdFont, info, 256 - 43, 101);

		drawButtonBG(256 - 53, 130, 42, 10, (bDragEnabled ? BUTTON_BG_BLUE : BUTTON_BG_WHITE), "Edit");
	}

	drawButtonBG(256 - 53, 160, 42, 18, BUTTON_BG_WHITE, "Clear");
}

bool DrawOutDrawnPosString(FILE *pfRecord)
{
	if (!cDrawnNum) return false;
	if (!pfRecord) return true;
	for (short s = 0; s < cDrawnNum; s++)
		for (u8 *p = cDrawnStrokes[s][0]; p < cDrawnStrokes[s][cDrawnPos[s]]; p+=2)
		{
			char chrPos[16];
			sprintf(chrPos, "%d,%d%s%s", p[0]*2, p[1]*2, (p < cDrawnStrokes[s][cDrawnPos[s]-1]?",":""), ((p==cDrawnStrokes[s][cDrawnPos[s]-1]&&s<cDrawnNum-1)?"/":""));
			fputs(chrPos, pfRecord);
		}
	return true;
}